package com.weiiew.calculationService.craft;

import com.example.feignapi.client.ItemClient;
import com.example.feignapi.client.RecipeClient;
import com.weiiew.calculationService.element.elEdge;
import com.weiiew.calculationService.element.elGraph;
import com.weiiew.calculationService.element.elNode;
import com.weiiew.common.entity.cal.TranslationRecipe;
import com.weiiew.common.entity.factory.Factory;
import com.weiiew.common.entity.factory.FactoryIngredient;
import com.weiiew.common.entity.factory.FactoryProduct;
import com.weiiew.common.entity.factory.FactoryRecipe;
import com.weiiew.common.entity.simple.DoublePair;
import com.weiiew.common.utils.BeautifyPrint;
import com.weiiew.common.exception.LockItemException;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import java.util.function.BiConsumer;

/**
 * @Description
 * @Author 魏锴
 * @Date 2025/3/6
 */
public class CraftCalculatorImpl2 implements CraftCalculator {
    private RecipeClient recipeService;
    private ItemClient itemService;
    public void setRecipeService(RecipeClient recipeService) {
        this.recipeService = recipeService;
    }
    public void setItemService(ItemClient itemService) {
        this.itemService = itemService;
    }
    private Factory factory;
    private int version;
    private Map<Integer, Integer> itemRecipeIdMap; // 记录物品id和配方id的关系
    private Map<Integer, List<DoublePair>> recipes; // 记录物品id和具体配方之间的关系
    private Map<Integer, CraftList> craftMap;// 记录物品id到合成记录的映射
    private Map<Integer, String> nameMap; // 记录物品id和物品名的映射
    private HashSet<Integer> ingoreItems; // 原料列表
    private int circle_item = -1; // 记录环的关键节点
    private static final boolean isTest = false;


    /**
     * 对整数进行整数输出，对小数进行小数输出，同时消除误差带来的影响
     * @param number 需要输出的数字
     * @return 输出的字符串
     */
    public static String toString(double number) {
        // 使用BigDecimal进行高精度四舍五入
        BigDecimal bd = new BigDecimal(number).setScale(2, RoundingMode.HALF_UP);
        // 判断四舍五入后是否等效于整数
        if (bd.remainder(BigDecimal.ONE).compareTo(BigDecimal.ZERO) == 0) {
            return bd.toBigInteger().toString();  // 返回整数形式
        } else {
            return bd.stripTrailingZeros().toPlainString();  // 返回去除末尾零的小数形式
        }
    }

    /**
     * 对输入数据进行规整化处理
     */
    public void validateConfig(){
        validateProducts(factory.getProducts());
        validateIngredients(factory.getIngredients());
    }

    // 产品校验逻辑
    private static void validateProducts(List<FactoryProduct> products) {
        if (products == null) {
            throw new IllegalArgumentException("产品列表不能为null");
        }
        Set<Integer> idSet = new HashSet<>();
        for (int i = 0; i < products.size(); i++) {
            FactoryProduct product = products.get(i);
            // 检查产品ID
            if (product.getId() == null) {
                throw new IllegalArgumentException(
                        String.format("产品列表第%d项：ID不能为null", i+1));
            }
            // 检查产品数量
            if (product.getNumber() <= 0) {
                throw new IllegalArgumentException(
                        String.format("产品ID %d：数量必须大于0（当前值：%d）",
                                product.getId(), product.getNumber()));
            }
            // 检查ID重复
            if (!idSet.add(product.getId())) {
                throw new IllegalArgumentException(
                        String.format("产品ID %d：存在重复项", product.getId()));
            }
        }
    }

    // 原料校验逻辑
    private static void validateIngredients(List<FactoryIngredient> ingredients) {
        if (ingredients == null) {
            throw new IllegalArgumentException("原料列表不能为null");
        }
        Set<Integer> idSet = new HashSet<>();
        for (int i = 0; i < ingredients.size(); i++) {
            FactoryIngredient ingredient = ingredients.get(i);
            // 检查原料ID
            if (ingredient.getId() == null) {
                throw new IllegalArgumentException(
                        String.format("原料列表第%d项：ID不能为null", i+1));
            }
            // 检查ID重复
            if (!idSet.add(ingredient.getId())) {
                throw new IllegalArgumentException(
                        String.format("原料ID %d：存在重复项", ingredient.getId()));
            }
        }
    }
    /**
     * 对输入数据进行预处理
     */
    public void pretreatment() {
        this.itemRecipeIdMap = itemService.getItemRecipeMap(this.version);// 获取初始配方映射
        this.nameMap = itemService.getIdNameMap(this.version);
        for (FactoryRecipe r : this.factory.getRecipes()) itemRecipeIdMap.put(r.getItem(), r.getRecipe());
        this.recipes = new HashMap<>();
        this.craftMap = new HashMap<>();
        this.ingoreItems = new HashSet<>();
        this.factory.getIngredients().forEach((ingredient -> this.ingoreItems.add(ingredient.getId())));
    }

    /**
     * 加载需要的配方数据
     */
    public void loadRecipes() throws LockItemException {
        Set<Integer> loaded_item = new HashSet<>();
        this.factory.getIngredients().forEach(factoryIngredient -> loaded_item.add(factoryIngredient.getId()));

        Stack<Integer> effect_item = new Stack<>();
        this.factory.getProducts().forEach(factoryProduct -> effect_item.push(factoryProduct.getId()));

        while (!effect_item.isEmpty()) {
            Integer item = effect_item.pop();
            if (loaded_item.contains(item)) continue; // 判断是否已经加载过
            loaded_item.add(item);
            Integer recipeId = this.itemRecipeIdMap.get(item);
            if (recipeId == null || recipeId == 0) throw new LockItemException(this.nameMap.get(item));
            List<DoublePair> recipe = recipeService.getFactoryRecipeById(item, recipeId);
            for (DoublePair value : recipe) {
                // 将未处理的物品加入影响列表
                if (value.getNumber() > 0) {
                    effect_item.push(value.getId());
                }
            }
            recipes.put(item, recipe);
            //进行配方处理完成
        }
    }


    /**
     * 进行数据的计算
     *
     * @return 计算结果
     */
    public CraftList calculation() {
        CraftList result = new CraftList();
        for (FactoryProduct item : factory.getProducts()) {
            CraftList list = getCraftList(item.getId(), new Stack<>()); // 获取一个物品的合成列表
            list.multiple(item.getNumber()); // 乘以物品需求数量
            result.add(list);
        }
        return result;
    }

    /**
     * 获取一单位id = itemId的物品的合成列表
     * @param itemId 物品id
     * @param effect_array 影响队列, 用于判断环的存在
     * @return 一单位id = index的物品的合成列表
     */
    private CraftList getCraftList(int itemId, Stack<Integer> effect_array) {
        if (craftMap.containsKey(itemId)){
            return craftMap.get(itemId).copy();
        }
        if (ingoreItems.contains(itemId)) return new CraftList();

        effect_array.push(itemId);
        boolean has_circle = calculationCraftlist(itemId, effect_array);// 进行计算
        if (has_circle && itemId == circle_item) { // 判断环是否截止
            circle_item = -1;
        }
        effect_array.pop();//移除影响队列
        CraftList list = craftMap.get(itemId).copy();
        if (has_circle) craftMap.remove(itemId); // 如何处于环内则删除合成列表以免影响后续节点
        if (list.get(itemId) == 0) throw new IllegalStateException("该物品合成循环无解");
        return list;
    }

    /**
     * 计算一单位id = itemId的物品的合成列表
     * @param itemId 物品id
     * @param effect_array 影响队列, 用于判断环的存在
     * @return 一单位id = itemId的物品的合成列表
     */
    private boolean calculationCraftlist(int itemId, Stack<Integer> effect_array) {
        CraftList result = new CraftList();
        List<DoublePair> items = recipes.get(itemId); //获取配方
        boolean in_circle = false;
        for (DoublePair item : items) {
            // 当该物品处于原材料列表时,该物品无需合成
            if (this.ingoreItems.contains(item.getId())) continue;
            if (item.getNumber() < 0) {// 该物品是多余物品
                result.specifyAdd(item.getId(), item.getNumber()); // 在相应位置设置多余物品
            } else if (effect_array.contains(item.getId())) {// 当该物品处于环内时
                in_circle = true;
                circle_item = item.getId();// 记录关键节点
                // 对环内物品进行处理
                result.specifyReduction(item.getId(), item.getNumber());// 将该物品视作原矿
            } else { // 当该物品不处于环内时
                CraftList list = getCraftList(item.getId(), effect_array);
                list.multiple(item.getNumber());
                result.add(list);
                if (circle_item != -1) in_circle = true;// 判断物品是否在环上
            }
        }
        result.specifyAdd(itemId, 1);
        result.scaleToOne(itemId);
        craftMap.put(itemId, result);
        return in_circle;
    }



    // 改进后的getRecipeNode方法
    public elNode getRecipeNode(Map.Entry<Integer, Double> entry) {
        int itemId = entry.getKey();
        Integer recipeId = itemRecipeIdMap.get(itemId);
        if (recipeId == null) {
            throw new IllegalStateException("物品" + nameMap.get(itemId) + "无对应配方");
        }
        TranslationRecipe recipe = recipeService.getTranslationRecipe(itemId, recipeId);
        double itemsPerMinute = (recipe.getItemNumber() * 60.0) / recipe.getTime();
        double buildingsNeeded = entry.getValue() / itemsPerMinute;
        String label = String.format("%s\n%s台%s",
                nameMap.get(itemId),
                toString(buildingsNeeded),
                recipe.getBuilding());
        return new elNode(NodePrefix.RECIPE + recipeId, label, 0);
    }

    /**
     * 转化节点
     * @param result 合成列表
     * @param graph 图
     */
    private void translationNodes(CraftList result, elGraph graph){
        for (FactoryIngredient ingredient : factory.getIngredients())
            graph.addNode(new elNode(NodePrefix.INGREDIENT + ingredient.getId(),
                    "提供" + this.nameMap.get(ingredient.getId()), 1));
        for (FactoryProduct product : factory.getProducts())
            graph.addNode(new elNode(NodePrefix.PRODUCT + product.getId(),
                    "需求" + this.nameMap.get(product.getId()) + "\n" + product.getNumber()
                            + "item/min", 2));
        for (Map.Entry<Integer, Double> entry : result.getStorage().entrySet()) {
            if (entry.getValue() > 0) graph.addNode(getRecipeNode(entry));
            else if (!this.ingoreItems.contains(entry.getKey()))
                graph.addNode(new elNode(NodePrefix.WASTE + entry.getKey(),
                    "多余" + this.nameMap.get(entry.getKey()) + "\n" + toString(-entry.getValue())
                            + "item/min", 3));
        }
    }

    BiConsumer<String, String> getConsumer(int item, double number, elGraph graph){
        return (source, target) -> {
            graph.addEdge(
                    new elEdge(this.nameMap.get(item) + " " + toString(Math.abs(number)) + "item/min",
                            source, target));
            if (isTest) System.out.println("添加边 -> source:" + source + "  target:" + target);
        };
    }

    /**
     * 转化边
     * @param result 合成列表
     * @param graph 图
     */
    private void translationEdges(CraftList result, elGraph graph){
        ItemChangeRecord record = new ItemChangeRecord();
        for (FactoryProduct p : factory.getProducts()){
            record.recordChange(p.getId(), p.getNumber(), NodePrefix.PRODUCT + p.getId(),
                    getConsumer(p.getId(), p.getNumber(), graph));
        }
        if (isTest) BeautifyPrint.print(record);
        for (Map.Entry<Integer, Double> entry : result.getStorage().entrySet()) {
            int itemId = entry.getKey();
            double number = entry.getValue();// 物品数量
            if (number > 0) {
                record.recordChange(itemId, -number, NodePrefix.RECIPE + this.itemRecipeIdMap.get(itemId),
                        getConsumer(itemId, number, graph));
                if (this.ingoreItems.contains(itemId)) continue;
                List<DoublePair> recipe = this.recipeService.getFactoryRecipeById(itemId, this.itemRecipeIdMap.get(itemId));
                for (DoublePair item : recipe) {
                    double ItemNumber = item.getNumber() * number;
                    record.recordChange(item.getId(), ItemNumber, NodePrefix.RECIPE +  this.itemRecipeIdMap.get(itemId),
                            getConsumer(item.getId(), ItemNumber, graph));
                }
            }
            else if (number < 0){
                //添加多余物品
                record.recordChange(itemId, -number, NodePrefix.WASTE + itemId,
                        getConsumer(itemId, number, graph));
            }
        }
        for (FactoryIngredient i : factory.getIngredients()){
            record.recordChange(i.getId(), -Double.MAX_VALUE, NodePrefix.INGREDIENT + i.getId(),
                    (source, target) -> {
                        graph.addEdge(new elEdge("提供" + this.nameMap.get(i.getId()), source, target));
                    });
        }
    }

    /**
     * 对计算结果进行翻译
     * @param result 合成线路计算结果
     * @return 转化完成的图
     */
    public elGraph translation(CraftList result) {
        elGraph graph = new elGraph();
        translationNodes(result, graph);
        translationEdges(result, graph);
        return graph;
    }

    @Override
    public elGraph calculation(Factory factory, int version) throws LockItemException {
        this.factory = factory;
        this.version = version;
        this.validateConfig(); // 输入校验
        this.pretreatment(); // 初始参数加载
        this.loadRecipes(); // 配方加载
        return translation(calculation()); // calculation()计算合成列表， translation() 转化参数
    }
}
